function Travel() {
    this._travelSteps = [];
    this._error = new Error();
}

Travel.prototype = {
    addStep: function(travelStep) {
        this._travelSteps.push(travelStep);
    },
    
    createAction: function(cancelTravelSteps) {
        var dateRange = TravelStep.getDateRange(this._travelSteps);
        var newTravelSteps = this._travelSteps.where(function(step) {
            return !step.getId();
        });

        var deletingTravelSteps = this._travelSteps.where(function(step) {
            return step.getId() && step.toBeDeleted;
        });
        return new TravelAction(
        {
            "add": newTravelSteps,
            "remove": (cancelTravelSteps || []).concat(deletingTravelSteps)
        }, dateRange);
    },

    isValid: function() {
        var me = this;
        var stepsWithoutDetetingOnes = this._travelSteps.where(function(step) {
            return !step.toBeDeleted;
        });
        stepsWithoutDetetingOnes.foreach(function(travelStep, index) {
            if (!travelStep.isValid(index)) {
                me._error.join(travelStep.getError());
            }
        });

        stepsWithoutDetetingOnes.foreach(function(step, i) {
            if (i == stepsWithoutDetetingOnes.length - 1) {
                return;
            }

            var nextStep = stepsWithoutDetetingOnes[i + 1];
            if (step.isDateValid() && nextStep.isDateValid()) {
                if (step.getArrivalDateTime() >= nextStep.getDepartureDateTime()) {
                    var stepArrivalViewIndex = step.arrivalViewIndex || i + 1;
                    var nextStepDepartureViewIndex = nextStep.departureViewIndex || i + 1;
                    var errorMessage = i18n("MSG_NEW_TRAVEL_STEPS_CONFLICT") + " " +  step.getArrivalLocation().fullname;
                    me._error.addError(errorMessage, DayDetailDialogViewMap.arrivalDate + stepArrivalViewIndex);
                    me._error.addError(errorMessage, DayDetailDialogViewMap.arrivalTime + stepArrivalViewIndex);
                    me._error.addError(errorMessage, DayDetailDialogViewMap.departureDate + nextStepDepartureViewIndex);
                    me._error.addError(errorMessage, DayDetailDialogViewMap.departureTime + nextStepDepartureViewIndex);
                }
            }
        });
        return !me._error.hasError();
    },
    hasSteps: function() {
        return this._travelSteps.length > 0;
    },
    getError: function() {
        return this._error;
    },
    getSortedTravelSteps: function() {
        return TravelStep.sortedTravelSteps(this._travelSteps);
    }
};


function TravelConflicts(travelSteps) {
    this.conflictTravelSteps = [];
    this.conflictMessages = [];
    this._travelSteps = travelSteps;
    this._existingTravelSteps = [];
}
TravelConflicts.prototype = {
    detectConflicts: function(existingTravelSteps) {
        var me = this;
        me._existingTravelSteps = existingTravelSteps;
        this._travelSteps.foreach(function(currentStep) {
            me._detectConflictsAgainstExistingTravelSteps(currentStep);
        });
        return this;
    },

    hasConflict: function() {
        return this.conflictTravelSteps.length > 0;
    },

    _detectConflictsAgainstExistingTravelSteps: function(currentStep) {
        this._detectGeneralConflicts(currentStep);
        this._detectNonConnectAfterTravelForDays(currentStep);
    },

    _detectGeneralConflicts: function(currentStep) {
        var me = this;
        this._existingTravelSteps.foreach(function(existingTravelStep) {
            if (currentStep.isConflictWith(existingTravelStep)) {
                me._addConflict(existingTravelStep, currentStep.departureViewIndex);
            }
        });
    },

    _detectNonConnectAfterTravelForDays: function(currentStep) {
        var me = this;
        currentStep.getDateRange().toDayArray().foreach(function(day) {
            me._detectNonConnectAfterTravelForDay(day, currentStep);
        });
    },

    _getTravelStepsInDay: function(day, currentStep) {
        var travelStepsInDay = [currentStep];
        this._existingTravelSteps.foreach(function(travelStep) {
            if (travelStep.containsDay(day.clearTime())) {
                travelStepsInDay.push(travelStep);
            }
        });
        return TravelStep.sortedTravelSteps(travelStepsInDay);
    },

    _detectNonConnectAfterTravelForDay: function(day, currentStep) {
        var sortedTravelStepsInDay = this._getTravelStepsInDay(day, currentStep);

        for (var i = 0; i < sortedTravelStepsInDay.length - 1; i++) {
            var thisTravelStep = sortedTravelStepsInDay[i];
            var nextTravelStep = sortedTravelStepsInDay[i + 1];

            
            if (!nextTravelStep.isConnectAfter(thisTravelStep)) {
                var confictTravelStep = currentStep.equals(thisTravelStep) ? nextTravelStep : thisTravelStep;
                this._addConflict(confictTravelStep, currentStep.departureViewIndex);
            }
        }
    },

    _addConflict: function(existingTravelStep, departureViewIndex) {
        if (!this.conflictMessages[departureViewIndex]) {
            this.conflictMessages[departureViewIndex] = [];
        }
        this.conflictTravelSteps.uniquePush(existingTravelStep);
        this.conflictMessages[departureViewIndex].uniquePush(existingTravelStep.toString());
    }
};

var DayDetailDialogViewMap = {
    location: 'daydetail_location_',
    departureDate: 'daydetail_departure_date_',
    departureTime: 'daydetail_departure_time_',
    arrivalDate: 'daydetail_arrival_date_',
    arrivalTime: 'daydetail_arrival_time_'
};
